package jannex.mapping;

import jannex.internal.ExceptionFactory;
import jannex.sql.JxArray;

import java.lang.reflect.*;
import java.lang.reflect.Array;
import java.sql.*;
import java.util.*;

public final class Fields {

    private Fields() {
    }

    public static <E> FieldSetter<E> convertFromArray(final Type type, final FieldSetter<E> setter) {
        return new FieldSetter<E>() {
            @Override
            public void set(E e, Object value) {
                if (value == null) {
                    setter.set(e, null);
                    return;
                }
                Collection<Object> c;
                if (List.class.isAssignableFrom(type.getClass())) {
                    c = new ArrayList<>();
                } else if (Set.class.isAssignableFrom(type.getClass())) {
                    c = new HashSet<>();
                } else {
                    c = new ArrayList<>();
                }

                if (value instanceof JxArray) {
                    value = ((JxArray) value).getArray();
                }
                Object[] array = (Object[]) value;
                Collections.addAll(c, array);
                setter.set(e, c);
            }
        };
    }

    public static <E> FieldGetter<E> convertToArray(final Type genericType, final FieldGetter<E> getter) {
        return new FieldGetter<E>() {
            @Override
            public Object get(E e) {
                @SuppressWarnings("unchecked")
                Object object = getter.get(e);
                if (object instanceof Collection) {
                    Class<?> componentType = Object.class;
                    if (genericType instanceof ParameterizedType) {
                        Type actualType = ((ParameterizedType) genericType).getActualTypeArguments()[0];
                        if (actualType instanceof Class) {
                            componentType = (Class<?>) actualType;
                        } else if (actualType instanceof WildcardType) {
                            Type upperBound = ((WildcardType) actualType).getUpperBounds()[0];
                            if (upperBound instanceof Class) {
                                componentType = (Class<?>) upperBound;
                            }
                        }
                    }
                    Collection<?> collection = (Collection<?>) object;
                    object = collection.toArray((Object[]) Array.newInstance(componentType, collection.size()));
                }
                return object;
            }
        };
    }

    public static <E> FieldSetter<E> setterFromField(final Field field) {
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
        return (e, value) -> {
            try {
                field.set(e, value);
            } catch (Exception ex) {
                throw ExceptionFactory.make(ex);
            }
        };
    }

    public static <E> FieldGetter<E> getterFromField(final Field field) {
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
        return e -> {
            try {
                return field.get(e);
            } catch (Exception ex) {
                throw ExceptionFactory.make(ex);
            }
        };
    }

    public static <E> FieldSetter<E> setterFromMethod(final Method method) {
        return (e, value) -> {
            try {
                method.invoke(e, value);
            } catch (Exception ex) {
                throw ExceptionFactory.make(ex);
            }
        };
    }

    public static <E> FieldGetter<E> getterFromMethod(final Method method) {
        return e -> {
            try {
                return method.invoke(e);
            } catch (Exception ex) {
                throw ExceptionFactory.make(ex);
            }
        };
    }
}
